Docker Image Secrets the Right Way
It is occasionally useful to pass sensitive information such as AWS credentials
(.aws/credentials
) or authentication secrets when building a Docker image.
Regardless of the reason, this information is sensitive and care must be taken
to ensure that it is not leaked to consumers of the Docker image.
Docker users make the mistake of passing sensitive information to their
image via --build-args
or as environment variables. This has resulted in
thousands of leaked secrets
in public Docker images.
The ARG Problem
It's tempting to pass secrets via --build-args
to the docker build
command. This
seems like the correct approach because the secret isn't hard-coded in the Dockerfile.
This is not what the ARG instruction is for and the issues caused by its use
are easy to demonstrate.
Here is an example Dockerfile
that demonstrates using the ARG
instruction
to set a secret for use in a curl
command.
FROM alpine
RUN apk update && apk add curl
ARG MY_SECRET
RUN curl -u "user:$MY_SECRET" https://ifconfig.me
Building this image requires the --build-arg
to set the MY_SECRET
variable. For
example: docker build . -t blogtest --build-arg MY_SECRET="abcdefg"
. Now the
image will use the value of MY_SECRET
in the curl
command.
The issue here is, again, that the ARG
instruction was not meant to handle secrets and
store the variable in the Docker image's metadata. Viewing the Docker image's layer
history reveals the value of the ARG
variable MY_SECRET
.
$ docker history blogtest
IMAGE CREATED CREATED BY SIZE COMMENT
d01dea2da844 17 minutes ago RUN |1 MY_SECRET=abcdefg /bin/sh -c curl -u … 0B buildkit.dockerfile.v0
<missing> 17 minutes ago ARG MY_SECRET 0B buildkit.dockerfile.v0
<missing> 17 minutes ago RUN /bin/sh -c apk update && apk add curl # … 4.35MB buildkit.dockerfile.v0
<missing> 6 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ADD file:df538113122843069… 5.33MB
BuildKit to the Rescue
Previous versions of Docker had no straightforward way of passing secrets when
building an image. It took great care and attention to detail to ensure that no
sensitive data remained in the image. The arrival of
Docker Buildkit
adds the --secret
option
which provides a secure mechanism for passing sensitive information to an image.
We can rewrite the above example using the new Buildkit options. This requires one
change to the Dockerfile
.
FROM alpine
RUN apk update && apk add curl
RUN --mount=type=secret,id=mysecret MY_SECRET=$(cat /run/secrets/mysecret ) \
&& curl -u "user:$MY_SECRET" https://ifconfig.me
We no longer need the ARG
instruction. Instead, we use the --mount
argument
with the RUN
instruction. This syntax mounts our secret to /run/secrets/
in
a file named after the id
option, in this case: mysecret
. The content of that
file is then stored in our variable, MY_SECRET
, so that we can use it in our curl
command.
We can take advantage of the updated Dockerfile
by adding the new --secret
argument to our build command. This example assumes we have set the environment
variable with our secret.
export MY_SECRET=abcdefg
docker build . -t blogtest --secret id=mysecret,env=MY_SECRET
Issuing the docker history
command no longer reveals our secret.
docker history blogtest
IMAGE CREATED CREATED BY SIZE COMMENT
ce5a8e6e0840 8 minutes ago RUN /bin/sh -c MY_SECRET=$(cat /run/secrets/… 0B buildkit.dockerfile.v0
<missing> 4 hours ago RUN /bin/sh -c apk update && apk add curl # … 4.35MB buildkit.dockerfile.v0
<missing> 6 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ADD file:df538113122843069… 5.33MB
Success! No more leaked secrets. The image can now be safely distributed.
A Couple Secret Secrets
There are a couple of rules to keep in mind when working with the --mount
argument
in a Dockerfile.
-
The secret you mount is available to its
RUN
instruction. Any followingRUN
instructions will not have access to that secret. -
The secret is mounted to a directory in
/run/secrets
named after the secret'sid
. -
The secret's destination can be set by adding the
dst
option. By default, the secret's filename will be theid
of the secret itself.
The --secret
argument of docker build
also has options which, at the time of
writing, are not well documented. One of the omissions is the source argument.
The example found in the Docker documentation demonstrates
the src=<filename>
usage. But looking at the source code
reveals that the env=<variable name>
is also a possible option (as demonstrated
in the above example).
Buildkit's secret handling is a great way to improve the security of your Docker images. Now is an excellent time to start adding it to your images. The feature will reduce the amount of leaked secrets and allow your ops teams to rest a bit more easily.